package org.stratusdb.funsql.statement;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;

import org.stratusdb.error.EnumError;
import org.stratusdb.error.Error;
import org.stratusdb.funsql.compile.operator.AbstractBinaryOperator;
import org.stratusdb.funsql.compile.operator.AbstractOperator;
import org.stratusdb.funsql.compile.operator.AbstractUnaryOperator;
import org.stratusdb.funsql.compile.operator.CartesianProduct;
import org.stratusdb.funsql.compile.operator.EnumOperator;
import org.stratusdb.funsql.compile.operator.EquiJoin;
import org.stratusdb.funsql.compile.operator.EquiSelection;
import org.stratusdb.funsql.compile.operator.SimpleProjection;
import org.stratusdb.funsql.compile.operator.TableOperator;
import org.stratusdb.funsql.compile.predicate.AbstractPredicate;
import org.stratusdb.funsql.compile.predicate.ComplexPredicate;
import org.stratusdb.funsql.compile.predicate.SimplePredicateAttribute;
import org.stratusdb.funsql.compile.predicate.SimplePredicateLiteral;
import org.stratusdb.funsql.compile.tokens.AbstractToken;
import org.stratusdb.funsql.compile.tokens.TokenAttribute;
import org.stratusdb.funsql.compile.tokens.TokenIntegerLiteral;
import org.stratusdb.funsql.compile.tokens.TokenLiteral;
import org.stratusdb.funsql.compile.tokens.TokenSchema;
import org.stratusdb.funsql.compile.tokens.TokenStringLiteral;
import org.stratusdb.funsql.compile.tokens.TokenTable;
import org.stratusdb.funsql.execute.BinaryOperator;
import org.stratusdb.funsql.execute.StoreScan;
import org.stratusdb.funsql.execute.UnaryOperator;
import org.stratusdb.metadata.Attribute;
import org.stratusdb.metadata.Catalog;
import org.stratusdb.metadata.Connection;
import org.stratusdb.metadata.Schema;
import org.stratusdb.metadata.Table;
import org.stratusdb.store.AbstractStore;
import org.stratusdb.store.EnumStore;
import org.stratusdb.types.AbstractSimpleType;
import org.stratusdb.types.AbstractType;
import org.stratusdb.types.SQLInteger;
import org.stratusdb.types.SQLVarchar;
import org.stratusdb.types.TableType;
import org.stratusdb.types.TupleType;

public class SelectStmt extends AbstractStatement {
	private Vector<TokenAttribute> tAttributes = new Vector<TokenAttribute>();
	private Vector<TokenTable> tTables = new Vector<TokenTable>();
	private AbstractPredicate tPredicate;

	private Vector<AbstractPredicate> tListPredicate = new Vector<AbstractPredicate>();

	private org.stratusdb.funsql.execute.AbstractOperator root = null;

	// constructors
	public SelectStmt() {
		this.statementType = EnumStatement.SELECT;
	}

	// getters and setters
	public void addAttribute(String att) {
		this.tAttributes.add(new TokenAttribute(att));
	}

	public void addAttribute(TokenAttribute att) {
		this.tAttributes.add(att);
	}

	public Collection<TokenAttribute> getAttributes() {
		return tAttributes;
	}

	public void addTable(String table) {
		this.tTables.add(new TokenTable(table));
	}

	public void addTable(TokenTable table) {
		this.tTables.add(table);
	}

	public Collection<TokenTable> getTables() {
		return tTables;
	}

	public AbstractPredicate getPredicate() {
		return tPredicate;
	}

	public void setPredicate(AbstractPredicate tPredicate) {
		this.tPredicate = tPredicate;
	}

	public Vector<AbstractPredicate> getAllPredicate() {
		return tListPredicate;
	}

	public void addPredicate(AbstractPredicate tPredicate) {
		this.tListPredicate.add(tPredicate);
	}

	@Override
	public Error compile() {
		@SuppressWarnings("unchecked")
		Vector<TokenAttribute> tempAttributes = (Vector<TokenAttribute>) tAttributes.clone();
		if (tempAttributes.size() == 0) {
			this.tAttributes.add(new TokenAttribute("*"));
		}
		int c = 0;
		for (TokenTable tt : tTables) {
			TokenSchema tSchema = tt.getSchema();
			Schema schema = Catalog.getSchema(tSchema.hashKey());
			Table t = Catalog.getTable(this.tTables.get(c++).hashKey(schema.getOid()));
			// Table should exist!
			if (t == null)
				System.out.println("t is null");
			if (t.checkObject() != Error.NO_ERROR)
				return t.checkObject();

			if (tempAttributes.size() != 0) {
				for (Attribute at : t.getAttributes()) {
					if (at.getName().equalsIgnoreCase("*")) {
						tempAttributes.clear();
						break;
					}

					for (TokenAttribute ta : tAttributes) {
						if (at.getName().equalsIgnoreCase(ta.getName().toString())) {
							tempAttributes.remove(ta);
						}
					}
				}
			}
		}

		// with this method we can't know, which attribute and table does not
		// exist
		if (!tempAttributes.isEmpty()) {
			String[] msg = new String[2];
			msg[0] = "randomAttribute";
			msg[1] = "randomTable";
			return new Error(EnumError.ATTRIBUTE_NOT_IN_TABLE, msg);
		}

		// create "tree"
		AbstractOperator lChild = null;
		for (TokenTable tt : this.tTables) {
			if (lChild == null) {

				lChild = new TableOperator(tt);
			} else {
				AbstractOperator rChild = new TableOperator(tt);
				lChild = new CartesianProduct(lChild, rChild);
			}
		}
		if (!this.tListPredicate.isEmpty() || this.tPredicate != null) {
			EquiSelection es = null;
			for (AbstractPredicate as : this.tListPredicate) {
				// first Selection? put lChild as the child
				if (es == null)
					es = new EquiSelection(lChild);
				else
					es = new EquiSelection(es);
				// ::TODO:: ugly.......
				// split the predicate
				String[] arr = as.toSqlString().split("=");
				// for every attribute, use the according literal
				if (as instanceof SimplePredicateAttribute) {
					es.setAttribtue(new TokenAttribute(arr[0]));
					es.setValue(new TokenAttribute(arr[1]));
				}
				if (as instanceof SimplePredicateLiteral) {
					try {
						es.setAttribtue(new TokenAttribute(arr[0]));
						es.setValue(new TokenIntegerLiteral(arr[1]));
					} catch (NumberFormatException e) {
						es.setAttribtue(new TokenAttribute(arr[0]));
						es.setValue(new TokenStringLiteral(arr[1]));
					}
				}
				// ugly end..., if complex predicate is added, reset the
				// instance here!
				ComplexPredicate.resetInstance();
			}
			lChild = es;
		}

		// fill the projection
		SimpleProjection projection = new SimpleProjection(lChild, this.tAttributes.size());
		for (int i = 0; i < this.tAttributes.size(); ++i) {
			projection.setTokenAttribute(i, this.tAttributes.get(i));
		}

		AbstractOperator tempRoot = projection;
		System.out.println("--------- Simple Tree ---------");
		printTree(tempRoot, "");

		createPrototype(tempRoot);
		System.out.println("-------------- Proto ---------");
		printTree(tempRoot, "");

		tempRoot = optimize(tempRoot);
		System.out.println("--------- Optimized Tree ---------");
		printTree(tempRoot, "");

		merge(tempRoot);
		System.out.println("--------- merge ---------");
		printTree(tempRoot, "");

		createPrototype(tempRoot);
		System.out.println("-------------- Proto ---------");
		printTree(tempRoot, "");

		{
			Error er = flagNode(tempRoot);
			// stop the compiling here, if an error occurs!
			if (er != Error.NO_ERROR)
				return er;
		}

		System.out.println("-------------- Flag ---------");
		printTree(tempRoot, "");

		projectionMerge(tempRoot, null);
		System.out.println("------------- Projection merged --------------");
		printTree(tempRoot, "");

		this.root = makeExeOp(tempRoot);
		System.out.println("------------ Execution Tree ----------");
		printTree(this.root, "");

		return Error.NO_ERROR;
	}

	/**
	 * Sets a SimpleProjection before every Operator if the Operator is
	 * performed by Stratus.
	 * 
	 * @param node
	 *            the Operator in the Tree
	 * @param projectionAttris
	 *            the Projection list
	 */
	private void projectionMerge(AbstractOperator node, Set<TokenAttribute> projectionAttris) {
		if (node instanceof SimpleProjection) {
			// the first Projection in the tree, just save the attributes
			if (projectionAttris == null) {
				projectionAttris = new HashSet<TokenAttribute>();
				SimpleProjection sp = (SimpleProjection) node;
				for (TokenAttribute ta : sp.getTokenAttributes())
					projectionAttris.add(ta);
				projectionMerge(sp, projectionAttris);
				return;
			} else {
				AbstractOperator child = ((SimpleProjection) node).getChild();
				Connection c = child.getConnection();
				// if child supports the simpleProjection, stop here!
				if (c != null && ((!c.equals(Connection.getStratusDummy())) && EnumStore.getStoreObject(c).supportsOperator(EnumOperator.SIMPLE_PROJECTION))) {
					node.setConnection(child.getConnection());
				} else {
					node.setConnection(Connection.getStratusDummy());
					projectionMerge(child, projectionAttris);
				}
				return;
			}
		}
		if (node instanceof EquiSelection) {
			// insert a new SimpleProjection between the EquiSelection and the
			// child of it
			EquiSelection es = (EquiSelection) node;
			projectionAttris.addAll(es.getPredicates().keySet());
			SimpleProjection sp = new SimpleProjection(es.getChild(), projectionAttris.size());
			if (sp.getChild().getConnection().equals(Connection.getStratusDummy()) || !EnumStore.getStoreObject(sp.getChild().getConnection()).supportsOperator(EnumOperator.SIMPLE_PROJECTION)) {
				sp.setConnection(Connection.getStratusDummy());
			} else {
				sp.setConnection(sp.getChild().getConnection());
			}
			int c = 0;
			for (TokenAttribute ta : projectionAttris) {
				sp.setTokenAttribute(c++, ta);
			}
			es.setColumns(es.getChild().getColumns());
			es.setChild(sp);
			projectionMerge(sp, projectionAttris);
			return;
		}
		if (node instanceof AbstractBinaryOperator) {
			if (!node.getConnection().equals(Connection.getStratusDummy()) && !EnumStore.getStoreObject(node.getConnection()).supportsOperator(EnumOperator.SIMPLE_PROJECTION)) {
				return;
			}
			if (node instanceof EquiJoin) {
				projectionAttris.add(((EquiJoin) node).getLeftTokenAttribute());
				projectionAttris.add(((EquiJoin) node).getRightTokenAttribute());
			}
			AbstractBinaryOperator cp = (AbstractBinaryOperator) node;
			// left case
			AbstractOperator left = cp.getLeftChild();
			Set<TokenAttribute> stal = new HashSet<TokenAttribute>();
			for (TokenAttribute ta : projectionAttris) {
				if (left.getColumns().keySet().contains(ta.toSqlString()))
					stal.add(ta);
			}
			SimpleProjection spl = new SimpleProjection(left, stal.size());
			if (spl.getChild().getConnection().equals(Connection.getStratusDummy()) || !EnumStore.getStoreObject(spl.getChild().getConnection()).supportsOperator(EnumOperator.SIMPLE_PROJECTION)) {
				spl.setConnection(Connection.getStratusDummy());
			} else {
				spl.setConnection(spl.getChild().getConnection());
			}
			int c = 0;
			for (TokenAttribute ta : stal) {
				spl.setTokenAttribute(c++, ta);
			}
			spl.setColumns(spl.getChild().getColumns());
			cp.setLeftChild(spl);
			if (left.getConnection().equals(Connection.getStratusDummy()))
				projectionMerge(spl, stal);
			// right case
			AbstractOperator right = cp.getRightChild();
			Set<TokenAttribute> star = new HashSet<TokenAttribute>();
			for (TokenAttribute ta : projectionAttris) {
				if (right.getColumns().keySet().contains(ta.toSqlString()))
					star.add(ta);
			}
			SimpleProjection spr = new SimpleProjection(right, star.size());
			if (spr.getChild().getConnection().equals(Connection.getStratusDummy()) || !EnumStore.getStoreObject(spr.getChild().getConnection()).supportsOperator(EnumOperator.SIMPLE_PROJECTION)) {
				spr.setConnection(Connection.getStratusDummy());
			} else {
				spr.setConnection(spr.getChild().getConnection());
			}
			c = 0;
			for (TokenAttribute ta : star) {
				spr.setTokenAttribute(c++, ta);
			}
			spr.setColumns(spr.getChild().getColumns());
			cp.setRightChild(spr);
			if (right.getConnection().equals(Connection.getStratusDummy()))
				projectionMerge(spr, star);
			return;
		}
		if (node instanceof TableOperator)
			return;
		if (node instanceof AbstractUnaryOperator) {
			System.out.println("u might have added an AbstractUnaryOperator... big mistake, trust me");
			projectionMerge(((AbstractUnaryOperator) node).getChild(), projectionAttris);
			return;
		}
	}

	private void printTree(AbstractOperator node, String tiefe) {
		System.out.println(tiefe + node.toString());

		if (node instanceof TableOperator)
			return;
		if (node instanceof AbstractUnaryOperator) {
			printTree(((AbstractUnaryOperator) node).getChild(), tiefe + "    ");
			return;
		}
		if (node instanceof AbstractBinaryOperator) {
			printTree(((AbstractBinaryOperator) node).getLeftChild(), tiefe + "    ");
			printTree(((AbstractBinaryOperator) node).getRightChild(), tiefe + "    ");
			return;
		}
	}

	private void printTree(org.stratusdb.funsql.execute.AbstractOperator node, String tiefe) {
		System.out.println(tiefe + node.toString());

		if (node instanceof StoreScan)
			return;
		if (node instanceof UnaryOperator) {
			printTree(((UnaryOperator) node).getChild(), tiefe + "    ");
			return;
		}
		if (node instanceof BinaryOperator) {
			printTree(((BinaryOperator) node).getLeftChild(), tiefe + "    ");
			printTree(((BinaryOperator) node).getRightChild(), tiefe + "    ");
			return;
		}
	}

	/**
	 * Merge all EquiSelection in a path
	 * 
	 * @param node
	 *            the operator
	 */
	private void merge(AbstractOperator node) {
		// only EquiSelection are interesting
		AbstractOperator ao = node;
		if (node instanceof TableOperator)
			return;

		if (node instanceof AbstractBinaryOperator) {
			merge(((AbstractBinaryOperator) node).getLeftChild());
			merge(((AbstractBinaryOperator) node).getRightChild());
			return;
		}

		if (node instanceof EquiSelection) {
			AbstractOperator parent = node;
			// within an EquiSelection are two different structures to save a
			// predicate, a list and a pair
			// we added it at the beginning to the pair, so fill the list now
			// for several EquiSelection!
			((EquiSelection) node).addPredicate(((EquiSelection) node).getAttribtue(), ((EquiSelection) node).getValue());
			while (!(((AbstractUnaryOperator) ao).getChild() instanceof AbstractBinaryOperator)) {
				if (((AbstractUnaryOperator) ao).getChild() instanceof TableOperator)
					return;
				parent = ao;
				ao = ((AbstractUnaryOperator) ao).getChild();
				if (ao instanceof EquiSelection) {
					((AbstractUnaryOperator) parent).setChild(((AbstractUnaryOperator) ao).getChild());
					EquiSelection es = (EquiSelection) ao;
					((EquiSelection) node).addPredicate(es.getAttribtue(), es.getValue());
				}
			}
			ao = ((AbstractUnaryOperator) ao).getChild();
			merge(((AbstractBinaryOperator) ao).getLeftChild());
			merge(((AbstractBinaryOperator) ao).getRightChild());
			return;
		}
		merge(((AbstractUnaryOperator) node).getChild());
		return;
	}

	/**
	 * Pushes down the Selection and merges a selection with the cartesian
	 * product to a join
	 * 
	 * @param node
	 *            the actual operator
	 * @return the node
	 */
	private AbstractOperator optimize(AbstractOperator node) {
		if (node instanceof TableOperator)
			return node;
		if (node instanceof AbstractBinaryOperator) {
			((AbstractBinaryOperator) node).setLeftChild(optimize(((AbstractBinaryOperator) node).getLeftChild()));
			((AbstractBinaryOperator) node).setRightChild(optimize(((AbstractBinaryOperator) node).getRightChild()));
		}
		if (node instanceof AbstractUnaryOperator) {
			if (node instanceof EquiSelection) {
				EquiSelection es = (EquiSelection) node;
				AbstractToken at = es.getValue();
				// right is a col
				if (at instanceof TokenAttribute) {
					AbstractOperator ao = es;
					while (((AbstractUnaryOperator) ao).getChild().getType() != EnumOperator.CARTESIAN_PRODUCT) {
						ao = ((AbstractUnaryOperator) ao).getChild();
						if (ao instanceof TableOperator)
							return node;
					}
					AbstractUnaryOperator parent = (AbstractUnaryOperator) ao;
					ao = parent.getChild();
					HashMap<String, Integer> tmpLeft = ((AbstractBinaryOperator) ao).getLeftChild().getColumns();
					HashMap<String, Integer> tmpRight = ((AbstractBinaryOperator) ao).getRightChild().getColumns();
					EquiSelection es1 = (EquiSelection) node;
					if (tmpLeft.containsKey(es1.getValue().toSqlString()) && tmpRight.containsKey(es1.getAttribtue().toSqlString())) {
						EquiJoin ej = new EquiJoin(((AbstractBinaryOperator) ao).getLeftChild(), ((AbstractBinaryOperator) ao).getRightChild(), es1.getAttribtue(), (TokenAttribute) es1.getValue());
						parent.setChild(ej);
						return ((AbstractUnaryOperator) node).getChild();
					}
					if (tmpRight.containsKey(es1.getValue().toSqlString()) && tmpLeft.containsKey(es1.getAttribtue().toSqlString())) {
						EquiJoin ej = new EquiJoin(((AbstractBinaryOperator) ao).getLeftChild(), ((AbstractBinaryOperator) ao).getRightChild(), es1.getAttribtue(), (TokenAttribute) es1.getValue());
						parent.setChild(ej);
						return ((AbstractUnaryOperator) node).getChild();
					}
				}
				// right is a literal
				else {
					AbstractOperator ao = es;
					AbstractOperator retVal = ((AbstractUnaryOperator) node).getChild();
					while (!(((AbstractUnaryOperator) ao).getChild() instanceof AbstractBinaryOperator)) {
						ao = ((AbstractUnaryOperator) ao).getChild();
						if (ao instanceof TableOperator)
							return node;
					}
					AbstractBinaryOperator parent = (AbstractBinaryOperator) ((AbstractUnaryOperator) ao).getChild();
					HashMap<String, Integer> tmpLeft = ((AbstractBinaryOperator) parent).getLeftChild().getColumns();
					HashMap<String, Integer> tmpRight = ((AbstractBinaryOperator) parent).getRightChild().getColumns();
					if (tmpLeft.containsKey(es.getAttribtue().toSqlString())) {
						es.setChild(parent.getLeftChild());
						parent.setLeftChild(es);
						es.setColumns(es.getChild().getColumns());
						retVal = optimize(retVal);
						return retVal;
					}
					if (tmpRight.containsKey(es.getAttribtue().toSqlString())) {
						es.setChild(parent.getRightChild());
						parent.setRightChild(es);
						es.setColumns(es.getChild().getColumns());
						retVal = optimize(retVal);
						return retVal;
					}

				}
			}
			((AbstractUnaryOperator) node).setChild(optimize(((AbstractUnaryOperator) node).getChild()));
		}
		return node;
	}

	/**
	 * Flags every Operator in the Tree with the according columns
	 * 
	 * @param node
	 *            the actual Operator for creating the Prototype
	 */
	private void createPrototype(AbstractOperator node) {
		// get all columns from the table
		if (node instanceof TableOperator) {
			TableOperator to = (TableOperator) node;
			TokenTable tt = to.getTokenTable();
			TokenSchema test = tt.getSchema();
			Schema schema = Catalog.getSchema(test.hashKey());
			Table t = Catalog.getTable(tt.hashKey(schema.getOid()));
			node.setColumns(t.getColumns());
			return;
		}
		// just get down
		else if (node instanceof AbstractUnaryOperator) {
			createPrototype(((AbstractUnaryOperator) node).getChild());
		} else if (node instanceof AbstractBinaryOperator) {
			createPrototype(((AbstractBinaryOperator) node).getLeftChild());
			createPrototype(((AbstractBinaryOperator) node).getRightChild());
		}
		switch (node.getType()) {
		case CARTESIAN_PRODUCT:
		case EQUI_JOIN:
			AbstractBinaryOperator ab = (AbstractBinaryOperator) node;
			HashMap<String, Integer> tmp = ab.getLeftChild().getColumns();
			// SQL columns starts with 1
			int counter = tmp.size();
			// merge the left with the right columns
			for (String sr : ab.getRightChild().getColumns().keySet()) {
				tmp.put(sr, ab.getRightChild().getColumn(sr) + counter);
			}
			node.setColumns(tmp);
			break;
		case SIMPLE_PROJECTION:
			SimpleProjection sp = (SimpleProjection) node;
			// SQL columns starts with 1
			counter = 1;
			// just get the columns, which are in the projection
			for (TokenAttribute ta : sp.getTokenAttributes()) {
				String str = ta.getName().getName();
				if (sp.getChild().getColumns().containsKey(str)) {
					sp.addColumn(str, counter++);
				}
			}
			break;
		case EQUI_SELECTION:
			// get the columns from the child
			AbstractUnaryOperator au = (AbstractUnaryOperator) node;
			au.setColumns(au.getChild().getColumns());
			break;
		default:
			System.out.println("Case not supported.");
		}

	}

	/**
	 * transforms the compile tree into an execute Tree
	 * 
	 * @param node
	 *            the compile operator
	 * @return the according execute Operator
	 */
	private org.stratusdb.funsql.execute.AbstractOperator makeExeOp(AbstractOperator node) {
		org.stratusdb.funsql.execute.AbstractOperator retVal = null;
		// generate a StoreScan for the top node of a part tree whose connection
		// is not stratus
		if (node.getConnection() != null && !node.getConnection().equals(Connection.getStratusDummy())) {
			AbstractStore as = EnumStore.getStoreObject(node.getConnection());
			retVal = new StoreScan(as, node.getConnection(), as.generateQuery(node), null);
		} else { // cost function should be here, hard coded physical for each
					// logical operator
			switch (node.getType()) {
			case SIMPLE_PROJECTION: {
				SimpleProjection sp = (SimpleProjection) node;
				// child's connection isn't stratus -> sp to exeSp
				if (!sp.getChild().getConnection().equals(Connection.getStratusDummy())) {
					Vector<Integer> tmp = new Vector<Integer>();
					for (TokenAttribute ta : sp.getTokenAttributes()) {
						tmp.add(sp.getChild().getColumn(ta.toSqlString()));
					}
					return new org.stratusdb.funsql.execute.SimpleProjection(makeExeOp(sp.getChild()), tmp);
				}
				// merge the projection within the child
				switch (sp.getChild().getType()) {
				case EQUI_SELECTION:
					EquiSelection es = (EquiSelection) sp.getChild();
					HashMap<Integer, AbstractSimpleType> attrList = new HashMap<Integer, AbstractSimpleType>();
					for (TokenAttribute ta : es.getPredicates().keySet()) {
						if (es.getPredicates().get(ta) instanceof TokenLiteral) {
							TokenLiteral tl = (TokenLiteral) es.getPredicates().get(ta);
							switch (tl.getType()) {
							case SQL_INTEGER_LITERAL:
								attrList.put(es.getColumn(ta.toSqlString()) - 1, new SQLInteger(((TokenIntegerLiteral) tl).getValue().intValue()));
								break;
							case SQL_STRING_LITERAL:
								attrList.put(es.getColumn(ta.toSqlString()) - 1, new SQLVarchar(((TokenStringLiteral) tl).getValue()));
								break;
							default:
								System.out.println("Humm, shouldn't happen!");
							}
						}
					}
					Vector<Integer> tmp = new Vector<Integer>();
					for (TokenAttribute ta : sp.getTokenAttributes()) {
						tmp.add(sp.getChild().getColumn(ta.toSqlString()));
					}
					return new org.stratusdb.funsql.execute.EquiSelection(makeExeOp(es.getChild()), attrList, tmp);
				case EQUI_JOIN:
					EquiJoin ej = (EquiJoin) sp.getChild();
					Vector<Integer> tmp2 = new Vector<Integer>();
					for (TokenAttribute ta : sp.getTokenAttributes()) {
						tmp2.add(sp.getChild().getColumn(ta.toSqlString()));
					}
					// -1, because we start counting @ 1 because of SQL
					int left = ej.getLeftChild().getColumn(ej.getLeftTokenAttribute().toSqlString()) - 1;
					int right = ej.getRightChild().getColumn(ej.getRightTokenAttribute().toSqlString()) - 1;
					// return new
					// org.stratusdb.funsql.execute.NestedLoopJoin(makeExeOp(ej.getLeftChild()),makeExeOp(ej.getRightChild()),left,right,tmp2);
					return new org.stratusdb.funsql.execute.HashEquiJoin(makeExeOp(ej.getLeftChild()), makeExeOp(ej.getRightChild()), left, right, tmp2);
				case CARTESIAN_PRODUCT:
					CartesianProduct cp = (CartesianProduct) sp.getChild();
					Vector<Integer> tmp3 = new Vector<Integer>();
					for (TokenAttribute ta : sp.getTokenAttributes()) {
						tmp3.add(sp.getChild().getColumn(ta.toSqlString()));
					}
					return new org.stratusdb.funsql.execute.NestedCartesianProduct(makeExeOp(cp.getLeftChild()), makeExeOp(cp.getRightChild()), tmp3);
				default:
					System.out.println("Operator  " + sp.getChild().getType() + " not supported.");
					return null;
				}
			}
			}
			System.out.println("We schouldn't be here, ::TBI:: This StratusDB Operator is not supported yet " + node.getType());
		}
		return retVal;
	}

	/**
	 * Flags the Operator with the right Connection
	 * 
	 * @param node
	 *            actual Operator
	 * @return the occurring Error
	 */
	// ::TODO:: check errors
	private Error flagNode(AbstractOperator node) {
		// if(node.getConnection() != null) return Error.NO_ERROR;
		if (node instanceof TableOperator) {
			TableOperator to = (TableOperator) node;
			TokenSchema test = to.getTokenTable().getSchema();
			Schema schema = Catalog.getSchema(test.hashKey());
			Connection con = Catalog.getConnection(schema.getOid());
			to.setConnection(con);
			return Error.NO_ERROR;
		}
		if (node instanceof AbstractBinaryOperator) {
			AbstractBinaryOperator ab = (AbstractBinaryOperator) node;
			flagNode(ab.getLeftChild());
			flagNode(ab.getRightChild());

			Connection conStore = ab.getLeftChild().getConnection();
			if (conStore == ab.getRightChild().getConnection() && EnumStore.getStoreObject(conStore).supportsOperator(node.getType())) {
				node.setConnection(conStore);
			} else {
				// Dummy value for stratus operators
				node.setConnection(Connection.getStratusDummy());
			}
			return Error.NO_ERROR;
		}
		if (node instanceof AbstractUnaryOperator) {
			AbstractUnaryOperator au = (AbstractUnaryOperator) node;
			flagNode(au.getChild());
			Connection conStore = au.getChild().getConnection();
			if (conStore.equals(Connection.getStratusDummy())) {
				node.setConnection(Connection.getStratusDummy());
				return Error.NO_ERROR;
			}
			if (EnumStore.getStoreObject(conStore).supportsOperator(node.getType()))
				node.setConnection(conStore);
			else
				node.setConnection(Connection.getStratusDummy());
			return Error.NO_ERROR;
		}
		return Error.NO_ERROR;
	}

	@Override
	public Error execute() {
		Error lastError = Error.NO_ERROR;
		lastError = this.root.open();
		// for Client Server, just return the last Error here
		TableType res;
		int i = 0;
		while ((res = this.root.next()) != null) {
			for (TupleType tt : res.getTuples()) {
				StringBuilder erg = new StringBuilder();
				if (tt == null)
					break;
				System.out.println("Tuple " + ++i);
				for (AbstractType at : tt.getValues()) {
					if (at == null)
						break;
					if (at instanceof SQLVarchar)
						erg.append(((SQLVarchar) at).getValue() + "\t");
					if (at instanceof SQLInteger)
						erg.append(((SQLInteger) at).getValue() + "\t");
				}
				System.out.println(erg);
			}
		}
		lastError = this.root.close();
		return lastError;
	}

	public TableType getResult() {
		TableType erg = this.root.next();
		if (erg == null)
			this.root.close();
		return erg;
	}
}
